package com.ooono.factorytest.items;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.ooono.factorytest.R;
import com.ooono.factorytest.base.BaseActivity;
import com.ooono.factorytest.view.CircleAnimaView;

import java.util.Locale;

public class GyroscopeTestActivity extends BaseActivity {

    private static final String TAG = "GSensorTestActivity";
    public static final String KEY = "gyroscope";

    private SensorManager sensorManager;
    private Sensor gyroscope;
    private GyroscopeEventListener gyroscopeEventListener;

    private LinearLayout circleAnimaContainer;
    private CircleAnimaView circleView;

    private TextView valueOfAxisX;
    private TextView valueOfAxisY;
    private TextView valueOfAxisZ;
    private TextView calibrationDta;
    private float[] mData = {0, 0, 0};

    @Override
    public int getTestTitle() {
        return R.string.gyroscope_test_title;
    }

    @Override
    public String getTestKey() {
        return KEY;
    }

    @Override
    public int getContentLayout() {
        return R.layout.content_gyroscope_test;
    }

    @Override
    public void initViews() {
        circleAnimaContainer = findViewById(R.id.circle_anima_container);
        circleView = new CircleAnimaView(this);
        circleAnimaContainer.addView(circleView);

        valueOfAxisX = findViewById(R.id.value_of_axis_x);
        valueOfAxisY = findViewById(R.id.value_of_axis_y);
        valueOfAxisZ = findViewById(R.id.value_of_axis_z);
        calibrationDta = findViewById(R.id.calibration_data_tv);
        calibrationDta.setText(String.format(Locale.ENGLISH, "%+8.4f,%+8.4f,%+8.4f",
                mData[0], mData[1], mData[2]));

        successBtn.setVisibility(View.INVISIBLE);

        initSensor();
    }

    @Override
    protected void onResume() {
        super.onResume();

        sensorManager.registerListener(gyroscopeEventListener, gyroscope,
                SensorManager.SENSOR_DELAY_GAME);
    }

    @Override
    protected void onPause() {
        sensorManager.unregisterListener(gyroscopeEventListener, gyroscope);
        super.onPause();
    }

    private void initSensor() {
        sensorManager = (SensorManager) this
                .getSystemService(Context.SENSOR_SERVICE);
        gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);

        gyroscopeEventListener = new GyroscopeEventListener();
    }

    private class GyroscopeEventListener implements SensorEventListener {

        private static final float NS2S = 1.0f / 1000000000.0f;
        private float timestamp;

        private float radiansAroundZ;// radians around Z axis
        private float radiansAroundY;// radians around Y axis
        private float radiansAroundX;// radians around X axis

        private final float[] ballCoords = new float[2];
        private boolean isBallTouchCircle = false;

        private float startRadians = 0;


        @Override
        public void onSensorChanged(SensorEvent event) {

            // show datas
            valueOfAxisX.setText(String.format(Locale.ENGLISH, "%+11.7f", event.values[0]));
            valueOfAxisY.setText(String.format(Locale.ENGLISH, "%+11.7f", event.values[1]));
            valueOfAxisZ.setText(String.format(Locale.ENGLISH, "%+11.7f", event.values[2]));

            if (timestamp != 0) {
                Log.d("values[x]", event.values[2] + "");
                final float dT = (event.timestamp - timestamp) * NS2S;
                float[] values = event.values;

                if (isBallTouchCircle) {// Ball touches the circle,get data of arounding the Z axis to update position of ball

                    if (circleView.isDetectedComplete()) {
                        circleView.invalidate();// draw the latest ball and circle
                        sensorManager.unregisterListener(
                                gyroscopeEventListener, gyroscope);
                        successBtn.setVisibility(View.VISIBLE);
                        return;
                    }

                    // transform angular speed around Z axis to radians
                    radiansAroundZ += values[2] * dT;
                    // reset radiansAroundZ
                    if (Math.abs(radiansAroundZ) > 2 * Math.PI) {
                        if (radiansAroundZ > 0) {
                            radiansAroundZ -= (float) (2 * Math.PI);
                        } else {
                            radiansAroundZ += (float) (2 * Math.PI);
                        }
                    }
                    circleView.setGreenCircleEndRadians(radiansAroundZ);
                } else {// ball doesn't touch circel,get data of X and Y axis
                    // transform angular speed around Y axis to radians
                    radiansAroundY += values[1] * dT;
                    // reset radiansAroundZ
                    if (Math.abs(radiansAroundY) > 2 * Math.PI) {
                        if (radiansAroundY > 0) {
                            radiansAroundY -= (float) (2 * Math.PI);
                        } else {
                            radiansAroundY += (float) (2 * Math.PI);
                        }
                    }

                    // transform angular speed around X axis to radians
                    radiansAroundX += values[0] * dT;
                    // reset radiansAroundZ
                    if (Math.abs(radiansAroundX) > 2 * Math.PI) {
                        if (radiansAroundX > 0) {
                            radiansAroundX -= (float) (2 * Math.PI);
                        } else {
                            radiansAroundX += (float) (2 * Math.PI);
                        }
                    }
                    // if angle between -45 and 45 then update position of ball which will move in circle
                    if (Math.abs(radiansAroundX) <= Math.PI / 4
                            && Math.abs(radiansAroundY) <= Math.PI / 4) {
                        isBallTouchCircle = calcBallCoords(radiansAroundY,
                                radiansAroundX, ballCoords);

                        Log.d(TAG, "ballCoords=" + "(" + ballCoords[0] + ","
                                + ballCoords[1] + ")");

                        if (isBallTouchCircle) {// ball touches circle
                            // calculate clock-wise radians between coords of ball and point (2*radius,radius)
                            startRadians = getBallRadians(ballCoords,
                                    radiansAroundY, radiansAroundX);
                            circleView.setStartRadians(startRadians);// set position of ball
                            circleView.setIsBallTouchCircle(isBallTouchCircle);
                        } else {// ball dosen't touches circle,set position of ball by data from X axis and Y axis
                            circleView.setBallCoords(ballCoords);
                        }
                    }
                }
            }
            timestamp = event.timestamp;
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }
    }

    /**
     * @param radiansAroundY radians around Y axis
     * @param radiansAroundX radians around X axis
     * @param ballCoords     array to store coords of ball
     * @return return true if ball touches circle,else return false
     */
    private boolean calcBallCoords(float radiansAroundY, float radiansAroundX,
                                   float[] ballCoords) {
        float[] circleCoords = circleView.getCircleCoords();
        // max moving distance of ball(inside radius subtract radius of ball)
        float maxDistance = circleView.getInsideRadius()
                - circleView.getBallRadius();

        float distanceX = getDisdance(radiansAroundY, maxDistance);// moving distance of ball on X axis
        float distanceY = getDisdance(radiansAroundX, maxDistance);// moving distance of ball on Y axis
        Log.d(TAG, "distanceX=" + distanceX + ",distanceY" + distanceY);
        // the real moving distance of ball
        float distance = (float) Math.sqrt(distanceX * distanceX + distanceY
                * distanceY);

        if (distance <= maxDistance) {// ball is still in circle,update position of circle
            ballCoords[0] = circleCoords[0] + distanceX;
            ballCoords[1] = circleCoords[1] + distanceY;
        }

        if (maxDistance - distance < 1) {// ball touches the circle
            Log.d("ballTouchCircle", "ballCoords=(" + ballCoords[0] + ","
                    + ballCoords[1] + ")");
            return true;
        }

        return false;// ball dosen't touch the circle
    }

    /**
     * calculate positive radians value between coords of ball and point(2*radius,radius)
     *
     * @param ballCoords     coords of ball
     * @param radiansAroundY radians around Y axis
     * @param radiansAroundX radians around X axis
     * @return positive radians value between coords of ball and point(2*radius,radius)
     */
    private float getBallRadians(float[] ballCoords, float radiansAroundY,
                                 float radiansAroundX) {
        // max moving distance of ball(inside radius subtract radius of ball)
        float maxDistance = circleView.getInsideRadius()
                - circleView.getBallRadius();
        float distanceX = getDisdance(radiansAroundY, maxDistance);// moving distance of ball on X axis
        float distanceY = getDisdance(radiansAroundX, maxDistance);// moving distance of ball on Y axis
        // the real moving distance of ball
        float distance = (float) Math.sqrt(distanceX * distanceX + distanceY
                * distanceY);

        float radians = Math.abs((float) Math.asin(distanceY / distance));

        float[] circleCoords = circleView.getCircleCoords();
        if (ballCoords[0] > circleCoords[0] && ballCoords[1] > circleCoords[0]) {// first quadrant
            // radians = radians;
        } else if (ballCoords[0] < circleCoords[0]
                && ballCoords[1] > circleCoords[0]) {// second quadrant
            radians = (float) (Math.PI - radians);
        } else if (ballCoords[0] < circleCoords[0]
                && ballCoords[1] < circleCoords[0]) {// third quadrant
            radians = (float) (Math.PI + radians);
        } else {// forth quadrant
            radians = (float) (2 * Math.PI - radians);
        }
        return radians;
    }

    /**
     * calculate moving distance of ball depend on randians
     *
     * @param radiansAroundAxis radians around axis
     * @return distance of ball,negative value indicate ball move to negative direction of x axis,
     * positive value indicate ball move to positive direction of x axis
     */
    private float getDisdance(float radiansAroundAxis, float maxDistance) {
        return (float) ((maxDistance * radiansAroundAxis) / (Math.PI / 4));
    }

}
